#include "TwitServer.h"
#include <errno.h>
#include <stdlib.h>
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <algorithm>

#define STDIN 0


const string TwitServer::LOG_FILE_NAME = "twitServer.log";

const string TwitServer::LOG_CONNECT_MSG = "\t connected";
const string TwitServer::LOG_DISCONNECT_MSG = "\t disconnected";
const string TwitServer::LOG_FOLLOW_MSG = "\t followed ";
const string TwitServer::LOG_UNFOLLOW_MSG = "\t unfollowed ";
const string TwitServer::LOG_TWIT_MSG = "\t twitted ";
const string TwitServer::LOG_DM_MSG = "\t direct messaged ";
const string TwitServer::LOG_PA_MSG = "\t announced ";
const string TwitServer::LOG_WHO_MSG = "\t Who";

const string TwitServer::USR_EXIT_CMD = "EXIT";


void TwitServer::writeToLog(string dataToWrite)
{
	_logStreamer << dataToWrite <<"."<<endl;
}

TwitServer::TwitServer(short int port) {
	//opening the log file:
	_logStreamer.open(LOG_FILE_NAME.c_str());

	//finding local IP address:
	char* name = new char[30];
	gethostname(name, 30);
	hostent* record = gethostbyname(name);

	//Initializing address struct:
	_myAddress = new struct sockaddr_in;
	_myAddress->sin_family = AF_INET;
	inet_aton(record->h_addr, &(_myAddress->sin_addr));
	_myAddress->sin_port = htons(port);
	memset(&(_myAddress->sin_zero), '\0', 8);

	//creating the socket:
	_listener = socket(PF_INET, SOCK_STREAM, 0);
	if (_listener == FAILURE) {
	}

	//binding the socket:
	if (bind(_listener, (struct sockaddr*) _myAddress, sizeof(struct sockaddr))== FAILURE) {
		exit(1);
	}

	//listening to the socket:
	listen(_listener, MAX_NUM_OF_CONNECTIONS);

	//adding the STDIN to the set of active sockets:
	FD_SET(STDIN,&_activeConnections);

	//adding the listening socket to the set of active sockets:
	FD_SET(_listener,&_activeConnections);

	_maxFD = _listener;

}

TwitServer::~TwitServer() {
	_logStreamer.close();

}



bool TwitServer::doesClientNameExists(string clientName)
{
	transform(clientName.begin(), clientName.end(), clientName.begin(), ::toupper);
	for (unsigned int i = 0; i < _clients.size(); i++) {
		string tmpName = _clients[i]->_name;
		transform(tmpName.begin(), tmpName.end(), tmpName.begin(), ::toupper);
		if(tmpName.compare(clientName) == 0)
		{
			return true;
		}
	}

	return false;
}

int TwitServer::connectToClient(int clientSocketFD)
{
	Message msg;

	//first read:
	Protocol::readMessage(clientSocketFD, msg.getMessage());
	string clientName((char*) msg.getMessageData());

	//checking client name is legal:
	if(doesClientNameExists(clientName))
	{
		string answer = Protocol::ERROR_MSG + ": Client name already exist";

		//sending WELCOME/ERROR message:
		msg.setMessageData(strdup(answer.c_str()));
		Protocol::sendMessage(clientSocketFD, msg.getMessageData(), answer.size()+1);
		return FAILURE;
	}
	else
	{
		client* newClient = new client;
		newClient->_name = clientName;
		newClient->_socketFd = clientSocketFD;
		_clients.push_back(newClient);

		//sending WELCOME message:
		msg.setMessageData(strdup(Protocol::WELCOME_MSG.c_str()));
		Protocol::sendMessage(clientSocketFD, msg.getMessageData(), Protocol::WELCOME_MSG.size()+1);

		string logLine = clientName + TwitServer::LOG_CONNECT_MSG;
		writeToLog(logLine);
		return SUCCESS;
	}
}



void TwitServer::listenToSockets() {
	int newClientSocket;
	unsigned int addressLength = sizeof(struct sockaddr_in);

	fd_set readfds;
	for (;;) {
		readfds = _activeConnections;
		if (select(_maxFD + 1, &readfds, NULL, NULL, NULL) == -1) {
			string errorMsg = Protocol::ERROR_MSG + "System call failed";
			cout<<errorMsg;
			disconnectFromClients();
			exit(-1);
		}
		// run through the existing connections looking for data to read
		for (int i = 0; i <= _maxFD; i++) {
			if (FD_ISSET(i, &readfds)) { // we got one!!
				//There's a new connection:
				if (i == _listener) {
					// handle new connections
					newClientSocket = accept(_listener,(struct sockaddr *) _myAddress, &addressLength);
					if(connectToClient(newClientSocket) == SUCCESS){
						// add to active connections set:
						FD_SET(newClientSocket, &_activeConnections);
						// keeping track of the maximum FD:
						if (newClientSocket > _maxFD)
						{
							_maxFD = newClientSocket;
						}
					}
				}
				//Handling input from STDIN:
				else if(i == STDIN)
				{
					string userCmd;
					getline(cin, userCmd);
					if(userCmd == USR_EXIT_CMD)
					{
						disconnectFromClients();
						exit(-1);
					}
				}
				//Handling existing client request:
				else if (FD_ISSET(i, &_activeConnections))
				{
					handleClientRequest(i);
				}
			}
		}
	}
}



void TwitServer::disconnectFromClients()
{
	Message msg;
	//sending BYE BYE message:

	for(unsigned int i=0; i<_clients.size(); i++)
	{
		msg.setMessageData(strdup(Protocol::DISCONNECT_MSG.c_str()));
		Protocol::sendMessage(_clients[i]->_socketFd, msg.getMessageData(),Protocol::DISCONNECT_MSG.size()+1);

		delete(_clients[i]);
	}
}


client* TwitServer::getClientByFD(int clientFD)
{
	vector<client*>::iterator itr;
	itr = _clients.begin();
	while(itr != _clients.end() && (*itr)->_socketFd!= clientFD)
	{
		itr++;
	}
	return *itr;
}



client* TwitServer::getClientByName(string clientName)
{
	transform(clientName.begin(), clientName.end(), clientName.begin(), ::toupper);
	vector<client*>::iterator itr;
	itr = _clients.begin();
	string capsName;
	while (itr != _clients.end()){
		capsName = (*itr)->_name;
		transform((*itr)->_name.begin(), (*itr)->_name.end(), capsName.begin(), ::toupper);
		if(capsName.compare(clientName) == 0)
		{
			return *itr;
		}
		itr++;
	}
	return *itr;
}



string getTimeString()
{

	time_t rawtime;
	struct tm* timeinfo;
	time(&rawtime);
	timeinfo = localtime(&rawtime);

	stringstream ss;
	int hourOfPost = timeinfo ->tm_hour;
	if(hourOfPost <= 9)
	{
		ss<<"0";
	}
	ss << hourOfPost <<":";
	int minOfPost = timeinfo->tm_min;
	if(minOfPost<=9)
	{
		ss<<"0";
	}
	ss<<minOfPost;

	return ss.str();
}

int TwitServer::disconnectClient(int clientFD)
{
	FD_CLR(clientFD, &_activeConnections);
	vector<client*>::iterator itr;
	itr = _clients.begin();
	int count=0;
	while(itr != _clients.end() && (*itr)->_socketFd!= clientFD)
	{
		itr++;
		count++;
	}
	string clientName = (*itr)->_name;
	_clients.erase(itr);

	//removing the client from the following list of other clients:
	for(unsigned int i=0; i<_clients.size(); i++)
	{
		_clients[i]->_followers.remove(clientFD);
	}

	Message msg;
	//sending BYE BYE message:
	msg.setMessageData(strdup(Protocol::BYE_BYE_MSG.c_str()));
	Protocol::sendMessage(clientFD, msg.getMessageData(),Protocol::BYE_BYE_MSG.size()+1);

	string logLine = clientName + TwitServer::LOG_DISCONNECT_MSG;
	writeToLog(logLine);
	return 0;
}




int TwitServer::setUserUnfollowing(int clientFD, string userToRemove)
{
	Message msg;

	string clientName = getClientByFD(clientFD)->_name;
	string logLine = clientName + TwitServer::LOG_UNFOLLOW_MSG + userToRemove;

	//If the client doesn't exist, the message Error: <SOME CLIENT NAME> does not exist should be sent to the requesting client.
	if (!doesClientNameExists(userToRemove)) {
		string errorMsg = Protocol::ERROR_MSG + userToRemove + " does not exist.";
		//sending an error message to the requesting client:
		msg.setMessageData(strdup(errorMsg.c_str()));
		Protocol::sendMessage(clientFD, msg.getMessageData(),errorMsg.size() + 1);
		writeToLog(logLine);
		return FAILURE;
	}

	//removing the requesting socket from the list of followers:
	client* requestingClient = getClientByFD(clientFD);
	client* clientToRemove = getClientByName(userToRemove);
	clientToRemove->_followers.remove(requestingClient->_socketFd);

	writeToLog(logLine);
	return SUCCESS;
}


int TwitServer::setUserFollowing(int clientFD, string followedClientName)
{
	Message msg;

	string clientName = getClientByFD(clientFD)->_name;
	string logLine = clientName + TwitServer::LOG_FOLLOW_MSG + followedClientName;

	if (!doesClientNameExists(followedClientName))
	{
		string errorMsg = Protocol::ERROR_MSG + followedClientName + " does not exist.";
		//sending an error message to the requesting client:
		msg.setMessageData(strdup(errorMsg.c_str()));
		Protocol::sendMessage(clientFD, msg.getMessageData(),errorMsg.size() + 1);
		writeToLog(logLine);
		return FAILURE;
	}
	//adding the requesting socket to the list of followers:
	client* clientToFollow = getClientByName(followedClientName);
	clientToFollow->_followers.push_back(clientFD);

	writeToLog(logLine);
	return SUCCESS;
}



int TwitServer::handleClientRequest(int clientFD)
{
	Message msg;
	//reading the request:

	Protocol::readMessage(clientFD, msg.getMessage());
	string line((char*) msg.getMessageData());
	int posOfFirstSpace = line.find(" ");
	string request;
	if(posOfFirstSpace>-1) {
		request = line.substr(0, posOfFirstSpace);
	}
	else
	{
		request = line;
	}
	if(request.compare(Protocol::DISCONNECT_MSG) == 0)
	{
		disconnectClient(clientFD);
	}
	else if (request.compare(Protocol::FOLLOW_MSG) == 0)
	{
		string clientToFollow = line.substr(posOfFirstSpace+1 , line.size()-1);
		setUserFollowing(clientFD, clientToFollow);
	}
	else if (request.compare(Protocol::UNFOLLOW_MSG) == 0)
	{
		string clientToRemove = line.substr(posOfFirstSpace+1 , line.size()-1);
		setUserUnfollowing(clientFD, clientToRemove);
	}
	else if (request.compare(Protocol::TWIT_MSG) == 0)
	{
		string twit = line.substr(posOfFirstSpace + 1, line.size() - 1);
		string timeString = getTimeString();
		postTwit(clientFD, twit, timeString);
	}
	else if (request.compare(Protocol::DIRECT_MESSAGE_MSG) == 0)
	{
		int posOfSeperator = line.find(Protocol::MSG_SEPARATOR);

		string recipient = line.substr(posOfFirstSpace + 1, posOfSeperator-posOfFirstSpace-1);
		string twit = line.substr(posOfSeperator + 1, line.size() - 1);
		string timeString = getTimeString();
		sendDirectMessage(clientFD, recipient, twit, timeString);
	}
	else if (request.compare(Protocol::PA_MESSAGE_MSG) == 0)
	{
		string twit = line.substr(posOfFirstSpace + 1, line.size() - 1);
		string timeString = getTimeString();
		sendPublicMessage(clientFD, twit, timeString);
	}
	else if (request.compare(Protocol::WHO_MSG) == 0)
	{
		sendListOfAllClients(clientFD);
	}

	else
	{
		disconnectClient(clientFD);
	}
	return 0;
}



int TwitServer::sendListOfAllClients(int clientFD)
{
	list<string> clientNames;
	for (unsigned int i=0; i<_clients.size(); i++)
	{
		clientNames.push_back(_clients[i]->_name);
	}
	clientNames.sort();

	Message msg;
	//generating the wanted string:
	list<string>::iterator namesItr;
	for (namesItr = clientNames.begin(); namesItr != clientNames.end(); namesItr++)
	{
		msg.setMessageData(strdup((*namesItr).c_str()));
		Protocol::sendMessage(clientFD, msg.getMessageData(),(*namesItr).length() + 1);
	}
	msg.setMessageData(strdup(Protocol::END_OF_LIST_MSG.c_str()));
	Protocol::sendMessage(clientFD, msg.getMessageData(),Protocol::END_OF_LIST_MSG.size() + 1);


	string clientName = getClientByFD(clientFD)->_name;
	string logLine = clientName + TwitServer::LOG_WHO_MSG;
	writeToLog(logLine);
	return SUCCESS;
}


int TwitServer::sendPublicMessage(int sendingClientFD, string twit, string timeString)
{

	Message msg;
	//generating the formatted twit:
	client* twittingClient = getClientByFD(sendingClientFD);
	stringstream ss;
	ss << timeString<<"::"<<twittingClient->_name<<" - "<<twit;
	string formattedTwit = ss.str();

	for (unsigned int i=0; i<_clients.size(); i++)
	{
		//skipping the sending client, he shouldn't recieve his own twit:
		if(_clients[i]->_socketFd == sendingClientFD)
		{
			continue;
		}
		msg.setMessageData(strdup(formattedTwit.c_str()));
		Protocol::sendMessage(_clients[i]->_socketFd, msg.getMessageData(),formattedTwit.length() + 1);
	}


	string clientName = getClientByFD(sendingClientFD)->_name;

	string logLine = clientName + TwitServer::LOG_PA_MSG + twit;
	writeToLog(logLine);
	return SUCCESS;

}





int TwitServer::sendDirectMessage(int sendingClientFD, string recipientName, string twit, string timeString)
{
	Message msg;

	string clientName = getClientByFD(sendingClientFD)->_name;
	string logLine = clientName + TwitServer::LOG_DM_MSG + recipientName + "\t " + twit;
	if (!doesClientNameExists(recipientName)) {
		string errorMsg = Protocol::ERROR_MSG + recipientName + " does not texist.";
		//sending an error message to the requesting client:
		msg.setMessageData(strdup(errorMsg.c_str()));
		Protocol::sendMessage(sendingClientFD, msg.getMessageData(),errorMsg.size() + 1);
		writeToLog(logLine);
		return FAILURE;
	}


	//generating the formatted twit:
	client* twittingClient = getClientByFD(sendingClientFD);
	stringstream ss;
	ss << timeString<< "::" << twittingClient->_name<< Protocol::MSG_SEPARATOR << recipientName<<"--"<<twit;
	string formattedTwit = ss.str();

	client* recipient = getClientByName(recipientName);

	msg.setMessageData(strdup(formattedTwit.c_str()));
	Protocol::sendMessage(recipient->_socketFd, msg.getMessageData(),formattedTwit.length()+1);

	writeToLog(logLine);

	return SUCCESS;
}




int TwitServer::postTwit(int clientFD, string twit, string timeString)
{
	Message msg;

	//generating the formatted twit:
	client* twittingClient = getClientByFD(clientFD);
	stringstream ss;
	ss << timeString<<"::"<<twittingClient->_name<<" - "<<twit;
	string formattedTwit = ss.str();

	//sending the twit to all of the client's followers:
	msg.setMessageData(strdup(formattedTwit.c_str()));
	list<int>::iterator followersItr;

	for (followersItr = twittingClient->_followers.begin(); followersItr != twittingClient->_followers.end(); followersItr++)
	{
		msg.setMessageData(strdup(formattedTwit.c_str()));
		Protocol::sendMessage(*followersItr, msg.getMessageData(),formattedTwit.length()+1);
	}
	string clientName = getClientByFD(clientFD)->_name;
	string logLine = clientName + TwitServer::LOG_TWIT_MSG+ twit;
	writeToLog(logLine);

	return SUCCESS;
}


int main(int argc, char **argv) {
	TwitServer* server = new TwitServer((short) strtoul(argv[1], NULL, 0));
	server->listenToSockets();
	return 0;
}
